package tr.edu.metu.is.mega.client.core;

import tr.edu.metu.is.mega.client.widgets.ConstraintDecorator;
import tr.edu.metu.is.mega.common.dto.BaseDTO;

import com.extjs.gxt.ui.client.event.ButtonEvent;
import com.extjs.gxt.ui.client.event.SelectionListener;
import com.extjs.gxt.ui.client.event.WindowEvent;
import com.extjs.gxt.ui.client.event.WindowListener;
import com.extjs.gxt.ui.client.widget.Window;
import com.extjs.gxt.ui.client.widget.button.Button;
import com.extjs.gxt.ui.client.widget.form.Field;
import com.extjs.gxt.ui.client.widget.form.FormPanel;
import com.extjs.gxt.ui.client.widget.layout.FitLayout;

/**
 * Abstract class for all edit dialogs.
 * 
 * @param <T> type modifier
 */
public abstract class EditDialog<T extends BaseDTO> extends Window {

    private FormPanel panelCenter;

    private T dtoObject;

    private DialogCallback<T> callback;

    private final Button buttonOK;

    private final Button buttonCancel;

    private final String className;

    private boolean updateMode;

    private final Object[] params;

    /**
     * Default constructor.
     * 
     * @param className class name
     */
    public EditDialog(String className) {
        this(className, null, null, null);
    }

    /**
     * Alternative constructor. This constructor calls "beforeInitForm"
     * procedure before call "initForm" procedure. Use this constructor if your
     * constructor takes argument and use this argument in "initForm" procedure.
     * 
     * @param className class name
     * @param dto DTO instance
     */
    public EditDialog(String className, BaseDTO dto, Object... params) {
        this(className, null, dto, params);
    }

    /**
     * This constructor calls "beforeInitForm" procedure before call "initForm"
     * procedure. <br>
     * Use this constructor if your constructor takes argument instead of dto.
     * 
     * @param className class name
     * @param type type name
     */
    public EditDialog(String className, String type, Object... params) {
        this(className, type, null, params);
    }

    /**
     * Hidden constructor.
     * 
     * @param className class name
     * @param type type
     * @param baseDTO dto instance
     */
    private EditDialog(String className, String type, BaseDTO baseDTO, Object... params) {
        this.className = className;
        this.params = params;
        setModal(true);
        setHeading("");
        setLayout(new FitLayout());

        if (type != null) {
            beforeInitForm(type);
        } else if (baseDTO != null) {
            beforeInitForm(baseDTO);
        }

        this.add(getPanelCenter());

        buttonOK = new Button("Save");
        buttonOK.addSelectionListener(new SelectionListener<ButtonEvent>() {

            @Override
            public void componentSelected(ButtonEvent ce) {
                save();
            }
        });
        addButton(buttonOK);
        buttonCancel = new Button("Cancel");
        buttonCancel.addSelectionListener(new SelectionListener<ButtonEvent>() {

            @Override
            public void componentSelected(ButtonEvent ce) {
                cancel();
            }
        });
        addButton(buttonCancel);
        this.addWindowListener(new WindowListener() {

            @Override
            public void windowShow(WindowEvent we) {
                manageConstraints();
            }
        });
    }

    /**
     * Make initializations before "initForm" procedure.
     * 
     * @param type type name
     */
    protected void beforeInitForm(String type) {
    }

    /**
     * Make initializations before "initForm" procedure.
     * 
     * @param baseDTO dto instance
     */
    protected void beforeInitForm(BaseDTO baseDTO) {
    }

    /**
     * Centers this before rendering.
     */
    protected void onRender() {
        center();
    }

    /**
     * Save action handler.
     */
    private void save() {
        for (Field<?> field : panelCenter.getFields()) {
            if (!field.isValid()) {
                return;
            }
        }
        // GUI to DTO
        dtoObject = guiToDTO();

        hide();
        callback.onOk(dtoObject);
    }

    /**
     * Initializes and returns panel center.
     * 
     * @return panelCenter
     */
    protected FormPanel getPanelCenter() {
        if (panelCenter == null) {
            panelCenter = new FormPanel();
            panelCenter.setHeaderVisible(false);
            panelCenter.setBodyBorder(false);
            initForm(panelCenter, params);
        }
        return panelCenter;
    }

    /**
     * Set constraints of components.
     */
    private void manageConstraints() {
        if (className != null) {
            ConstraintDecorator decorator = new ConstraintDecorator();
            decorator.decorateComponents(className, panelCenter);
        }
    }

    /**
     * Initializes GUI components.
     * 
     * @param parent parent form
     */
    protected abstract void initForm(FormPanel parent, Object... params);

    /**
     * Cancel action handler.
     */
    private void cancel() {
        this.dtoObject = null;
        hide();
        callback.onCancel();
    }

    /**
     * Creates an instance of entity.
     * 
     * @param dto DTO instance
     * @param dialogCallback callback handler
     */
    public final void create(T dto, DialogCallback<T> dialogCallback) {
        this.updateMode = false;
        this.callback = dialogCallback;
        this.dtoObject = dto;
        setReadOnly(false);
        show();
    }

    /**
     * Updates an instance of entity.
     * 
     * @param dto DTO instance to update
     * @param dialogCallback callback handler
     */
    public final void update(T dto, DialogCallback<T> dialogCallback) {
        this.updateMode = true;
        this.callback = dialogCallback;
        this.dtoObject = dto;
        setReadOnly(false);
        show();
        dtoToGUI(dto);

    }

    /**
     * Generates DTO instance from GUI widgets.
     * 
     * @return DTO instance
     */
    protected abstract T guiToDTO();

    /**
     * Displays a DTO instance.
     * 
     * @param dto DTO instance
     */
    protected void dtoToGUI(T dto) {
    }

    /**
     * Returns the entity.
     * 
     * @return the entity
     */
    protected T getEntity() {
        return dtoObject;
    }

    /**
     * Sets read only status of this.
     * 
     * @param readOnly read only indicator
     */
    public void setReadOnly(boolean readOnly) {
        getPanelCenter().setReadOnly(readOnly);
        buttonOK.setVisible(!readOnly);
        buttonCancel.setVisible(!readOnly);
    }

    /**
     * Show detail screen.
     * 
     * @param dto DTO instance
     */
    public final void showDetail(T dto) {
        setReadOnly(true);
        dtoToGUI(dto);
        show();
    }

    /**
     * Returns whether this dialog is opened for updating or creating.
     * 
     * @return true for updating
     */
    protected boolean isUpdateMode() {
        return updateMode;
    }

}
